package fnsvr

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"time"

	"github.com/redis/go-redis/v9"
)

// 创建redis连接
func CreateRedisConn(cfg *RedisConfig) (*redis.Client, error) {
	client := redis.NewClient(&redis.Options{
		Addr:     fmt.Sprintf("%s:%d", cfg.Host, cfg.Port),
		Password: cfg.Password,
		DB:       cfg.DbName,
	})

	ctx := context.Background()
	_, err := client.Ping(ctx).Result()
	if err != nil {
		return nil, err
	}

	return client, nil
}

// 设置key并设置过期时间
func SetKeyWithExpiration[T any](client redis.Cmdable, key string, value *T, expiration time.Duration) error {
	ctx := context.Background()

	// value序列化成json
	valueJson, err := json.Marshal(value)
	if err != nil {
		return err
	}

	return client.Set(ctx, key, valueJson, expiration).Err()
}

// 获取key
func GetKey[T any](
	client redis.Cmdable,
	key string,
) (*T, error) {
	ctx := context.Background()

	// 获取key
	valueJson, err := client.Get(ctx, key).Result()
	if err != nil {
		return nil, err
	}

	var value T

	// 反序列化json
	err = json.Unmarshal([]byte(valueJson), &value)
	if err != nil {
		return nil, err
	}

	return &value, nil
}

// 获取key并自动延长过期时间
func GetKeyWithExpiration[T any](
	client redis.Cmdable,
	key string,
	expiration time.Duration,
) (*T, error) {
	ctx := context.Background()

	value, err := GetKey[T](client, key)
	if err != nil {
		return nil, err
	}

	// 延长过期时间
	err = client.Expire(ctx, key, expiration).Err()
	if err != nil {
		return value, err
	}

	return value, nil
}

// 获取key并自动延长过期时间，如果key不存在则创建
func GetKeyWithExpirationAutoCreate[T any](
	client redis.Cmdable,
	key string,
	expiration time.Duration,
	createValue func() (*T, error),
) (*T, error) {
	value, err := GetKeyWithExpiration[T](client, key, expiration)
	if err != nil {
		v, err := createValue()
		if err != nil {
			return nil, err
		}
		err2 := SetKeyWithExpiration(client, key, v, expiration)
		if err2 != nil {
			return nil, err2
		}
		return v, nil
	}
	return value, nil
}

// 获取指定key的列表，并自动延长过期时间，如果key不存在则创建
func GetListExpirationAutoCreate[T any](
	client redis.Cmdable,
	key string,
	expiration time.Duration,
	createValue func() ([]T, error),
) ([]T, error) {
	ctx := context.Background()
	exists, err := client.Exists(ctx, key).Result()
	if err != nil {
		return nil, err
	}

	var result []T
	if exists == 1 {
		//列表存在
		elements, err := client.LRange(ctx, key, 0, -1).Result()
		if err != nil {
			return nil, err
		}

		//elements 转为 []T
		for _, element := range elements {
			var value T
			err = json.Unmarshal([]byte(element), &value)
			if err != nil {
				return nil, err
			}
			result = append(result, value)
		}
	} else {

		//列表不存在，创建
		list, err := createValue()
		if err != nil {
			return nil, err
		}

		if len(list) > 0 {

			//遍历list
			var values []any
			for _, item := range list {
				itemJson, err := json.Marshal(item)
				if err != nil {
					return nil, err
				}
				values = append(values, string(itemJson))
			}

			//创建的数据添加到redis
			_, err2 := client.RPush(ctx, key, values...).Result()
			if err2 != nil {
				return nil, err2
			}
		}

		result = list
	}

	//设置过期时间
	err = client.Expire(ctx, key, expiration).Err()
	if err != nil {
		return nil, err
	}

	return result, nil
}

// 跨进程锁
func CrossProcessLockWithWait(client redis.Cmdable, lockKey string, expire time.Duration, maxWait time.Duration) (string, error) {
	ctx := context.Background()
	value := fmt.Sprintf("%d", time.Now().UnixNano())
	start := time.Now()

	for {
		// 尝试获取锁
		success, err := client.SetNX(ctx, lockKey, value, expire).Result()
		if err != nil {
			return "", fmt.Errorf("获取锁时发生错误: %v", err)
		}

		if success {
			return value, nil
		}

		// 检查是否超时
		if time.Since(start) > maxWait {
			return "", errors.New("等待获取锁超时")
		}

		// 等待一段时间后重试
		time.Sleep(100 * time.Millisecond)
	}
}

// 跨进程释放锁
func CrossProcessReleaseLock(client redis.Cmdable, lockKey, value string) error {
	ctx := context.Background()
	script := `
	if redis.call("get", KEYS[1]) == ARGV[1] then
		return redis.call("del", KEYS[1])
	else
		return 0
	end
	`
	_, err := client.Eval(ctx, script, []string{lockKey}, value).Result()
	return err
}

// 批量原子增加哈希表中的key值
// fields map[哈希表中的key]增加值_可以是负数
// 当哈希表中的某个key值<=0时，自动被删除
func BatchAtomicIncreaseHashFields(rdb redis.Cmdable, hashKey string, fields map[string]int64) ([]int64, error) {

	script := `
	local hash = KEYS[1]
	local results = {}
	for i = 1, #ARGV, 2 do
		local field = ARGV[i]
		local delta = tonumber(ARGV[i+1])
		local current = redis.call('HINCRBY', hash, field, delta)
		if current <= 0 then
			redis.call('HDEL',hash, field)
		end
		results[#results+1] = current
	end
	return results
	`

	var args []interface{}
	for k, v := range fields {
		args = append(args, k, v)
	}

	ctx := context.Background()
	cmd := rdb.Eval(ctx, script, []string{hashKey}, args...)
	err := cmd.Err()
	if err != nil {
		return nil, err
	}

	if cmd.Val() == nil {
		return nil, nil
	}

	return cmd.Int64Slice()
}
